上一篇我們使用複合控制項(繼承 CompositeControl)的方式來實作 TBToolbar 控制項,本文將針對複合控制項做一些測試,說明在使用複合控制項要注意的一些問題。
程式碼下載:ASP.NET Server Control - Day15.rar
一、複合控制項建立子控制項的時機
還記得我們之前介紹複合控制項時有談到 CompositeControl 類別會確保我們存取子控制項時,它的子控制項一定會事先建立;也就是當我們使用 Controls 屬性去存取子控制項時,一定會執行 CreateChildControls 方法,以確保子控制項事先被建立。我們看一下 CompositeControl 類別的 Controls 屬性的寫法就可以了解其中的原由,在存取 CompositeControl.Controls 屬性時,它會先執行 Control.EnsureChildControls 方法;而 EnsureChildControls 方法會去判斷子控制項是否已建立,若未建立會去執行 CreateChildControls 方法,這也就是為什麼 CompositeControl 有辨法確保子控制項事先被建立的原因。
CompositeControl.Controls 屬性如下
Public Overrides ReadOnly Property Controls As ControlCollection
Get
Me.EnsureChildControls
Return MyBase.Controls
End Get
End Property
Control.EnsureChildControls 方法如下
Protected Overridable Sub EnsureChildControls()
If (Not Me.ChildControlsCreated AndAlso Not Me.flags.Item(&H100)) Then
Me.flags.Set(&H100)
Try
Me.ResolveAdapter
If (Not Me._adapter Is Nothing) Then
Me._adapter.CreateChildControls
Else
Me.CreateChildControls
End If
Me.ChildControlsCreated = True
Finally
Me.flags.Clear(&H100)
End Try
End If
End Sub
二、複合控制項隱藏的問題
我們以上篇的 TBToolbar 控制項為例,撰寫一些測試案例來說明複合控制項的問題。在撰寫測試案例之前,我們先修改一下 TBToolbar 控制項,覆寫 LoadViewState 及 SaveViewState 方法,將 Items 屬性儲存於 ViewState 中以維持狀態。
在測試頁面上放置「測試一」、「測試二」、「PostBack」三個按鈕,這三個按鈕的動作如下。
「測試一」按鈕:在工具列直接新增一個按鈕。
「測試二」按鈕:先使用 FindControl 取得工具列的按鈕,然後在在工具列再新增一個按鈕。
「PostBack」按鈕:單純執行 PostBack,不撰寫程式碼。
三個按鈕的程式碼如下所示。
Protected Sub Button1_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles
Button1.Click
Dim oItem As TBToolbarItem
'加入新按鈕
oItem = New TBToolbarItem()
oItem.Text = "新按鈕"
oItem.Key = "NewButton"
TBToolbar1.Items.Add(oItem)
Me.Response.Write("「測試一」按鈕")
End Sub
Protected Sub Button2_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles
Button2.Click
Dim oItem As TBToolbarItem
Dim oButton As Button
'先執行 FindControl 去取得 ID="Add" 的按鈕
oButton = TBToolbar1.FindControl("Add")
'再加入新按鈕
oItem = New TBToolbarItem()
oItem.Text = "新按鈕"
oItem.Key = "NewButton"
TBToolbar1.Items.Add(oItem)
Me.Response.Write("「測試二」按鈕")
End Sub
Protected Sub Button3_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles
Button3.Click
'單純 PostBack,無程式碼
Me.Response.Write("「PostBack」按鈕")
End Sub
案例一:執行「測試一」按鈕,在工具列直接新增一個按鈕。
當按下「測試一」按鈕時,工具列可以正常加入我們新增的按鈕。
案例二:執行「測試二」按鈕,先使用 FindControl 取得工具列的按鈕,然後在在工具列再新增一個按鈕。
重新執行程式,當按下「測試二」按鈕時,你會發現奇怪的現象,工具列竟然沒有加入我們新增的按鈕?
此時再按下「PostBack」按鈕,工具列才會出現我們剛剛加入的按鈕。
為什麼會發生這種怪現象呢?其實原因很簡單,因為 FindControl 時會去存取 Controls 屬性,而這時子控制項已經被建立了;而之前再用 Items 屬性加入新按鈕,它已經不會在重建子控制項,導致第一時間沒有加入新按鈕。不過 Items 屬性會被存在 ViewState 中,所以當執行「PostBack」按鈕時,就會出現我們剛剛新增的按鈕。
三、解決方式
要解決上述「測試二」的問題,只要覆寫 TBToolbar 控制項的 Render 方法,在 Render 前執行 RecreateChildControls 方法,強制重建子控制項。
''' <summary>
''' 覆寫 Render 方法。
''' </summary>
Protected Overrides Sub Render(ByVal writer As System.Web.UI.HtmlTextWriter)
Me.RecreateChildControls()
MyBase.Render(writer)
End Sub
再一次執行「測試二」的動作,就會發現執行結果就會正常了。
四、結語
在複合控制項的 Render 前執行 RecreateChildControls 方法可以強制重建子控制項,可是這樣又會引發另一個問題,那就是當直接存取子控制項去修改子控制項的屬性後,一旦在 Render 又重建子控制項,那之前設定子控制項狀態又被全部重建了,所以需特別注意有這樣的情形。另外複合控制項有可能重覆執行建立子控制的動作,在執行效能上也比較不佳。
備註:本文同步發佈於筆者「ASP.NET 魔法學院」部落格
http://www.dotblogs.com.tw/jeff377/archive/2008/10/16/5695.aspx